跳到主要内容

gRPC 使用-配置环境

Go 的 gRPC 官方库 Go 的 gRPC Quick start

搭建环境

安装 protoc

安装编译器,参考 Protocol Buffer Compiler Installation

# 最简单的方式
sudo apt install -y protobuf-compiler

也可以选择手动安装 官方资源库

# 这里可以只下载对应的编译器的
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-linux-x86_64.zip
unzip protoc-3.19.1-linux-x86_64.zip -d protobuf-3.19.1/

# 添加这个执行文件
vim ~/.bashrc
export PROTOBUF=/home/alsritter/tool/go/protobuf-3.19.1/
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin:$PROTOBUF/bin

# 更新
source ~/.bashrc
# 检查是否安装完成
protoc --version

安装 Golang 的依赖

安装 Golang 的编译插件

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1

更新路径

# 这一步其实可以省略
export PATH="$PATH:$(go env GOPATH)/bin"

将 Protoc Plugin 的可执行文件从 $GOPATH 中移动到 $GOBIN

mv $GOPATH/bin/protoc-gen-go $GOROOT/bin/

安装 gRPC 框架

go get -u google.golang.org/grpc

编译命令

$ protoc --help
Usage: protoc [OPTION] PROTO_FILES

-IPATH, --proto_path=PATH 指定搜索路径
--plugin=EXECUTABLE:

....

--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file

@<filename> proto文件的具体位置


  • --proto_path 或者 -I 参数用以指定所编译源码(包括直接编译的和被导入的 proto 文件)的搜索路径
  • --go_out:参数之间用逗号隔开,最后用冒号来指定代码目录架构的生成位置
  • --go_out=plugins=grpc:参数来生成 gRPC 相关代码,如果不加 plugins=grpc,就只生成 message 数据

其中:

--go_out=plugins=grpc,paths=import:. 。注意一下 paths 参数,他有两个选项,import 和 source_relative 。默认为 import ,代表按照生成的 go 代码的包的全路径去创建目录层级,source_relative 代表按照 protobuf 源文件的目录层级去创建 go 代码的目录层级,如果目录已存在则不用创建

# 这里就是读取 protos 目录下的 proto 文件
protoc -I . --go_out=plugins=grpc:. --go_opt=paths=source_relative protos/*.proto

实例

# 进入到 proto 目录中,输入
$ protoc *.proto --go_out=plugins=grpc:. --go_opt=paths=source_relative

# 或者在项目的根目录输入
# --go_opt=paths=source_relative 表示要用相对路径产生
$ protoc -I <src_proto_folder> --go_out=plugins=grpc:<dist_directory> --go_opt=paths=source_relative <src_proto_file_path>

$ protoc -I proto/jubox --go_out=plugins=grpc:proto/jubox --go_opt=paths=source_relative proto/jubox/jubox.proto

# 產生編譯檔(產生一般的 .pb.go 檔,但沒有使用 gRPC plugin
$ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto # 預設根據 go_package 路徑

# 產生 proto 檔,預設將根據 proto 中的 go_package 路徑
$ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto


# 另一種寫法會 build 出兩隻檔案,一支是 proto buffer(foobar.pb.go),一支是 gRPC 用的檔案(foobar_grpc.pb.go)
$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative {proto_filename}.proto

如何 import 其它文件

有时使用其它的三方插件,总是不可避免的会引用到其它的包,如下所示:

syntax = "proto3";

package helloworld;

import "google/api/annotations.proto";

// ...

那么如何安装这些 proto 呢?

其实很简单,引入这些三方 proto,只需在执行 protoc 命令时指定对应的路径就行了

首先下载 proto

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.14.5

下载之后就能在 github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis\google\api 目录下找到对应的 proto 文件了

20220601114330

执行完上述命令后,就会将 protoc-gen-grpc-gateway 下载到电脑的 GOPATH 下,自己电脑的 GOPATH 可以通过命令 go env 查看。

所以对应的就是

$GOPATH\pkg\mod\github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis

所以引用路径就是

# 注意: 路径中要用 '/'
protoc -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis \
--go_out=plugins=grpc:. ./*.proto

简单的使用例

编写一个 Hello World 程序

项目结构:

|—— hello/
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello/
|—— hello.proto // proto 描述文件
|—— hello.pb.go // proto 编译后文件

编写 proto 文件

编写描述文件:hello.proto

注意:下面这个 go_package 的路径必须有一个 /,网上的很多教程都是以前的,因为编译 .pb.go 调用的是 protoc-gen-go 插件完成,而这个插件 v1.4.0 以上的版本就要求必须加 /

具体生成的地址参考 Correct format of protoc go_package?

syntax = "proto3"; // 指定proto版本
package hello; // 指定默认包名

// 指定 golang 包名,编译到当前路径,并且使用 hello 包(前面是导出路径, ; 后面是包名)
option go_package = "./;hello";

// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}

// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}

hello.proto 文件中定义了一个 Hello Service,该服务包含一个 SayHello 方法,同时声明了 HelloRequest 和 HelloResponse 消息结构用于请求和响应。

客户端使用 HelloRequest 参数调用 SayHello 方法请求服务端,服务端响应 HelloResponse 消息。一个最简单的服务就定义好了。

编译生成 Go 文件

编译生成 .pb.go 文件

# 编译 hello.proto
#$ protoc -I . --go_out=plugins=grpc:. ./hello.proto
$ protoc --go_out=./proto \
--go-grpc_out=require_unimplemented_servers=false:./proto proto/hello.proto

这里实际上使用了两个插件,protoc-gen-goprotoc-gen-go-grpc,后者是生成 gRPC 代码,但是这个 protoc-gen-go-grpc 插件依赖于前者生成的 proto 结构体

如下:protoc-gen-go 生成的结构体:

// HelloRequest 请求结构
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields

Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}

注意,不要编辑编译生成的 hello.pb.go 文件

编写服务端

实现服务端接口 server/main.go

package main

import (
"context"
"fmt"
"net"

pb "stgrpc/proto/hello"

"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)

const (
// Address gRPC服务地址
ADDRESS = "127.0.0.1:50052"
)

// 定义 helloService 并实现约定的接口
type helloService struct{}

// HelloService Hello 服务
var HelloService = helloService{}

// SayHello 实现 Hello 服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}

func main() {
listen, err := net.Listen("tcp", ADDRESS)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}

// 实例化 grpc Server
s := grpc.NewServer()

// 注册 HelloService
pb.RegisterHelloServer(s, HelloService)

grpclog.Infoln("Listen on " + ADDRESS)
s.Serve(listen)
}

服务端引入编译后的 proto 包,定义一个空结构用于实现约定的接口,接口描述可以查看 hello.pb.go 文件中的 HelloServer 接口描述。实例化 grpc Server 并注册 HelloService,开始提供服务。

注意:因为这里使用了 grpclog 库,它内部调用的 LoggerV2 默认为 ERROR 级别

所以需要自己设置这个 GRPC_GO_LOG_SEVERITY_LEVEL 系统变量的级别

# ERROR、WARNING、INFO
$ export GRPC_GO_LOG_SEVERITY_LEVEL=INFO
# 再次运行就能打印日志了
$ go run server/main.go
# 2021/11/07 21:08:15 INFO: Listen on 127.0.0.1:50052

编写客户端

实现客户端调用 client/main.go

package main

import (
pb "stgrpc/proto/hello" // 引入proto包

"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)

const (
// Address gRPC服务地址
ADDRESS = "127.0.0.1:50052"
)

func main() {
// 连接
conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()

// 初始化客户端
c := pb.NewHelloClient(conn)

// 调用方法
req := &pb.HelloRequest{Name: "Hello gRPC !"}
res, err := c.SayHello(context.Background(), req)

if err != nil {
grpclog.Fatalln(err)
}

grpclog.Info(res.Message)
}

检查控制台

Reference